#ifndef RAY_TRACE_PRO_TEXTURE_DATA_H
#define RAY_TRACE_PRO_TEXTURE_DATA_H

#include "Basic/GxLibBasic.h"
#include "VectorMath.h"

namespace RayTracePro
{
	using namespace GxLibMath;

	struct Color
	{
		union
		{
			struct
			{
				unsigned char R,G,B,A;
			};
			int Value;
		};
	};
	struct TextureLevel
	{
		GxLibBasic::Array<Color, true> Pixels;
		int Width, Height;
	};
	class TextureData
	{
	public:
		int RefCount;
		GxLibBasic::String FileName;
		int Width, Height;
		GxLibBasic::Array<TextureLevel> Levels;
		TextureData(const GxLibBasic::String & fileName);
	};

	inline void SampleTextureLevel(GxLibMath::Vec4 * result, TextureData * texture, int lod, Vec2 & _uv)
	{
		Vec2 uv = _uv;
		TextureLevel & level = texture->Levels[lod];
		uv.x *= level.Width;
		uv.y *= level.Height;
		int i0 = (int)(uv.x);
		int i1 = i0+1>=level.Width?0:i0+1;
		int j0 = (int)(uv.y);
		int j1 = j0+1>=level.Height?0:j0+1;
		i0 = min(i0, level.Width-1);
		j0 = min(j0, level.Height-1);
		Color c1,c2,c3,c4;
		c1 = level.Pixels[j0 * level.Width + i0];
		c2 = level.Pixels[j0 * level.Width + i1];
		c3 = level.Pixels[j1 * level.Width + i0];
		c4 = level.Pixels[j1 * level.Width + i1];
		Color ci0, ci1;
		float it = uv.x - i0;
		float jt = uv.y - j0;
		float invIt = 1.0f - it;
		float invJt = 1.0f - jt;
		ci0.R = (unsigned char)(c1.R * invIt + c2.R * it);
		ci0.G = (unsigned char)(c1.G * invIt + c2.G * it);
		ci0.B = (unsigned char)(c1.B * invIt + c2.B * it);
		ci0.A = (unsigned char)(c1.A * invIt + c2.A * it);
		ci1.R = (unsigned char)(c3.R * invIt + c4.R * it);
		ci1.G = (unsigned char)(c3.G * invIt + c4.G * it);
		ci1.B = (unsigned char)(c3.B * invIt + c4.B * it);
		ci1.A = (unsigned char)(c3.A * invIt + c4.A * it);
		Color c;
		c.R = (unsigned char)(ci0.R * invJt + ci1.R * jt);
		c.G = (unsigned char)(ci0.G * invJt + ci1.G * jt);
		c.B = (unsigned char)(ci0.B * invJt + ci1.B * jt);
		c.A = (unsigned char)(ci0.A * invJt + ci1.A * jt);
		result->x = c.R / 255.0f;
		result->y = c.G / 255.0f;
		result->z = c.B / 255.0f;
		result->w = c.A / 255.0f;
	}

	inline void TrilinearSampling(GxLibMath::Vec4 * result, TextureData * texture, float du, float dv, Vec2 & uv)
	{
		du *= texture->Width;
		dv *= texture->Height;
		float maxDudv = max(du, dv);
		float lod;
		int lod1, lod2;
		if (maxDudv < 0.0001f)
		{
			lod = 0.0f;
			lod1 = lod2 = 0;
		}
		else
		{
			lod = max(0, log(maxDudv) * 1.442695f);
			lod1 = min((int)lod, texture->Levels.Count()-1);
			lod2 = min(lod1 + 1, texture->Levels.Count()-1);
		}
		if (lod1 == lod2)
		{
			SampleTextureLevel(result, texture, lod1, uv);
		}
		else
		{
			float lodt = lod - lod1;
			float invLodt = 1.0f - lodt;
			GxLibMath::Vec4 v1,v2;
			SampleTextureLevel(&v1, texture, lod1, uv);
			SampleTextureLevel(&v2, texture, lod2, uv);
			result->x = v1.x * invLodt + v2.x * lodt;
			result->y = v1.y * invLodt + v2.y * lodt;
			result->z = v1.z * invLodt + v2.z * lodt;
			result->w = v1.w * invLodt + v2.w * lodt;
		}
	}

	inline void AnisotropicSampling(GxLibMath::Vec4 * result, TextureData * texture, int maxRate, float dudx, float dvdx, float dudy, float dvdy, Vec2 & uv)
	{
		result->SetZero();
		float lodx, lody;
		float xlen = sqrt(dudx*dudx + dvdx*dvdx);
		float ylen = sqrt(dudy*dudy + dvdy*dvdy);
		int textureSize = max(texture->Width, texture->Height);
		lodx = max(0, log(xlen * textureSize) * 1.442695f);
		lody = max(0, log(ylen * textureSize) * 1.442695f);
		if (xlen > ylen)
		{
			int rate = ylen<0.0001f?maxRate:(int)(xlen/ylen + 0.5f);
			rate = min(maxRate, rate);
			rate = min(rate, 1<<(int)floor(lodx-lody + 0.5f));
			float invRate = 1.0f / rate;
			float startU = uv.x - dudx*0.5f;
			float startV = uv.y - dvdx*0.5f;
			float stepU = dudx * invRate;
			float stepV = dvdx * invRate;
			GxLibMath::Vec4 rs;
			for (int i = 0; i<rate; i++)
			{
				uv.x = startU + stepU * (i+0.5f);
				uv.y = startV + stepV * (i+0.5f);
				uv.x -= floor(uv.x);
				uv.y -= floor(uv.y);
				TrilinearSampling(&rs, texture, ylen, ylen, uv);
				(*result) += rs;
			}
			(*result) *= invRate;
		}
		else
		{
			int rate = xlen<0.0001f?maxRate:(int)(ylen/xlen + 0.5f);
			rate = min(maxRate, rate);
			rate = min(rate, 1<<(int)floor(lody-lodx + 0.5f));
			float invRate = 1.0f / rate;
			float startU = uv.x - dudy*0.5f;
			float startV = uv.y - dvdy*0.5f;
			float stepU = dudy * invRate;
			float stepV = dvdy * invRate;
			GxLibMath::Vec4 rs;
			for (int i = 0; i<rate; i++)
			{
				uv.x = startU + stepU * (i+0.5f);
				uv.y = startV + stepV * (i+0.5f);
				uv.x -= floor(uv.x);
				uv.y -= floor(uv.y);
				TrilinearSampling(&rs, texture, xlen, xlen, uv);
				(*result) += rs;
			}
			(*result) *= invRate;
		}
	}
}

#endif